package com.example.paopao.consumer.memer.filter;

import com.alibaba.fastjson.JSON;
import com.example.paopao.common.enumerator.ServiceCode;
import com.example.paopao.common.pojo.po.UserStatePO;
import com.example.paopao.common.pojo.security.CurrenPrincipal;
import com.example.paopao.common.web.JsonResult;
import com.example.paopao.consumer.memer.dao.cache.IUserCacheRepository;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    @Value("${paopao.jwt.secret-key}")
    private String secretKey;

    @Autowired
    private IUserCacheRepository userCacheRepository;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        /*获取本次请求的路径*/
        StringBuffer requestURL = request.getRequestURL();

        //TODO 对某些请求的 不需要携带JWT的请求 直接放行
        // 直接放行登录和注册的请求
        String[] permitUrls = {"/v1/users/login,/v1/alipay/pay,/v1/alipay/notify"};
        String requestURI = request.getRequestURI();
        if (Arrays.asList(permitUrls).contains(requestURI)) {
            filterChain.doFilter(request, response);
            return;
        }


        //JWT数据应该是请求头重名为Authorization的属性
        //从请求头中获取JWT数据
        String jwt=request.getHeader("Authorization");
        if (jwt==null){
            filterChain.doFilter(request,response);
            return;
        }


        /*检查是否获取到有效的JWT*/
        if (!StringUtils.hasText(jwt)||jwt.length()<113 || jwt.split("\\.").length!=3){
            filterChain.doFilter(request,response);
            return;
        }

        /*尝试解析JWT*/
        response.setContentType("application/json;charset=UTF-8");

        /*
        * claims对象是JWT解析后获得的有效负载(payload)部分的表示，在JWT中，有效负载包含了一些声明(Claims),可用于传递有关用户或实体的信息
        * JWT的有效负载通常包含一些标准声明，这些声明可以是任何键值对的形式，用于传递与应用程序相关的附加信息
        * 通过解析JWT并获取到claims对象，您可以进一步访问和使用其中的声明。可以从claims对象中提取用户ID、角色、权限等信息，以进行身份验证和授权操作
        * */
        Claims claims=null;
        try{
            claims= Jwts.parser() /*创建一个JWT解析器*/
                    .setSigningKey(secretKey) /*设置用于验证签名的秘钥*/
                    .parseClaimsJws(jwt) /*方法解析传入的JWT字符串*/
                    .getBody(); /*方法获取JWT中的payload部分，并将其存储在claims变量中作进一步处理或验证*/
        }catch (SignatureException e){
            /*验证签名失败*/
            String message="非法访问,本次访问已经被记录！";
            JsonResult jsonResult=JsonResult.fail(ServiceCode.ERR_JWT_SIGNATURE,message);
            String jsonString = JSON.toJSONString(jsonResult);
            PrintWriter writer = response.getWriter();
            writer.println(jsonString);
            writer.close();
            return;
        }catch (MalformedJwtException e){
            /*数据格式错误*/
            String message="非法访问,本次访问已经被记录！";
            JsonResult jsonResult=JsonResult.fail(ServiceCode.ERR_JWT_SIGNATURE,message);
            String jsonString = JSON.toJSONString(jsonResult);
            PrintWriter writer = response.getWriter();
            writer.println(jsonString);
            writer.close();
            return;
        }catch (Throwable e){
            String message="操作失败，服务器忙，请稍后再试！";
            JsonResult jsonResult=JsonResult.fail(ServiceCode.ERR_JWT_SIGNATURE,message);
            String jsonString = JSON.toJSONString(jsonResult);
            PrintWriter writer = response.getWriter();
            writer.println(jsonString);
            writer.close();
            return;
        }

        /*从解析结果中获取用户的身份信息*/
        Long userId = claims.get("id", Long.class);
        String username = claims.get("username", String.class);
        String remoteAddr = claims.get("remoteAddr", String.class);
        String header = claims.get("header", String.class);

        /*检查JWT是否存在盗用行为*/
        if(!remoteAddr.equals(request.getRemoteAddr())
            && !header.equals(request.getHeader("header"))){
            /*
            * Jwt中记录的IP与浏览器信息，与本次请求中的IP与浏览器均不同视为盗用
            * 放行，由后续的组件进行处理
            * */
            filterChain.doFilter(request,response);
            return;
        }

        /*从Redis中获取用户的状态*/
        UserStatePO userState = userCacheRepository.getUserState(userId);
        if (userState==null){
            //没有在Redis中获得有效的用户状态，则视为“未通过认证”，放行，由后续的过滤器进行下一步处理
            filterChain.doFilter(request,response);
            return;
        }


        if (!userState.getIsStater()){
            // TODO 删除用户在Redis中的id 使用户强制下线
            userCacheRepository.deleteUserState(userId);
            String message="您当前账号已经被禁用，将强制下线!";
            JsonResult result = JsonResult.fail(ServiceCode.ERROR_UNAUTHORIZED_DISABLED, message);
            String jsonString = JSON.toJSONString(result);
            PrintWriter writer = response.getWriter();
            writer.println(jsonString);
            writer.close();
            return;
        }

        // TODO 对Redis中的用户状态数据进行续期
        userCacheRepository.renewal(userId);

        //准备当事人数据
        CurrenPrincipal currenPrincipal=new CurrenPrincipal();
        currenPrincipal.setUserId(userId);
        currenPrincipal.setUsername(username);

        //准备权限列表，需要是符合框架的要求的数据格式，即collection<? extends GrantedAuthority> authorities
        String authorJsonString = userState.getAuthorJsonString();
        /*
        * 将JSON数组的字符串authorJsonString解析为List<String> 对象
        * */
        List<String> stringList = JSON.parseArray(authorJsonString, String.class);
        /*
        * 创建一个空的List<GrantedAuthority>对象，用于存储权限对象
        * GrantedAuthority：是一个spring security框架中的一个接口，用于表示用户或实体的授权信息
        * 在spring security中，授权信息用于用户是否具有特定操作或访问资源的权限。GrantedAuthority接口定义了授权信息的基本方法和属性，以及默认实现类
        * 通常情况下，GrantedAuthority对象表示用户所拥有的某个权限或角色，可以使用SimpleGrantedAuthority类来创建一个简单的GrantedAuthority对象
        * 其中参数为权限或角色的字符串值
        * Spring Security在进行身份验证和授权时，会使用GrantedAuthority对象进行权限的比较和匹配，以决定用户是否被允许执行某个操作访问某个资源
        * 通过将GrantedAuthority对象组成的列表与用户身份相关联，Spring security可以实现细粒度的权限管理和访问控制
        * */
        List<GrantedAuthority> authorities=new ArrayList<>();
        for (String authority : stringList) {
            /*
            * SimpleGrantedAuthority是Spring Security框架中提供的一个实现了Granthority接口的简单类
            * SimpleGrantedAuthority类的作用是表示用户或实体的授权信息，具体而言，它封装了一个权限或角色的字符串值，这个类通常与创建和表示用户所拥有的权限和角色
            * 带参构造函数SimpleGrantedAuthority(*) 用于创建一个包含指定权限或角色字符串值的SimpleGrantedAuthority对象。该构造函数接受一个字符串参数authority，并将其设置为对象的权限或角色
            * 在Spring security中，SimpleGrantedAuthority经常与其他类一起使用，以描述用户的身份和授权信息，并进行权限的验证和访问控制
            * */
            GrantedAuthority grantedAuthority=new SimpleGrantedAuthority(authority);
            authorities.add(grantedAuthority);
        }

        //将解析得到的用户身份信息创建为Authentication并存入到SecurityContext
        /*
        * Authentication authentication：此处创建的是一个Authentication对象，再SpringSecurity中，Authentication表示用户的身份验证信息，它包含了用户的主题(currentPrincipal)
        * 权限（authorites）以及其他相关的认证信息
        *
        * UsernamePasswordAuthenticationToken:这是Authentictical接口的一个实现类，用于给予用户名和密码的身份验证，它接受三个参数：
        * 当前主体（currenPrincipal），凭证/密码（null）,以及权限集合(authorities)
        * */
        Authentication authentication
                =new UsernamePasswordAuthenticationToken(currenPrincipal,null,authorities);

        /*
        * SecurityContext securityContext:创建一个SecurityContext对象。SecurityConText是SpringSecurity框架中用于存储当前线程的安全上下文信息的容器
        * 它通常包含了当前用户的认证信息(Authentication对象)以及其他与安全相关的数据
        *
        * SecurityContextHolder.getContext():SecurityContextHolder是用于访问和管理SecurityContext的工具类。getContext()方法返回当前线程的SecurityContext对象
        * */
        SecurityContext securityContext= SecurityContextHolder.getContext();

        /*
        * securityContext.setAuthentication(authentication)：将之前创建的Authentication对象设置到SecurityContext中，以便将认证信息与当前线程关联起来
        * 这样后续的安全检查和操作中，系统可以识别用户已经通过身份认证
        * */
        securityContext.setAuthentication(authentication);

        //放行
        filterChain.doFilter(request,response);
    }
}
